﻿using System;
using System.Xml.Linq;
using System.Collections.Generic;
using ComTypes = System.Runtime.InteropServices.ComTypes;

namespace NFox.Runtime.Com.Reflection
{

    /// <summary>
    /// Com类型信息
    /// </summary>
    public class Type
    {

        public Assembly Assembly { get; }

        public string Name { get; }

        public bool IsCollection { get; }

        public Dictionary<string, Field> Fields { get; }

        public Dictionary<string, Function> Functions { get; }

        public Dictionary<string, Property> Properties { get; }

        internal Type(Assembly ass, string fileName)
        {
            Assembly = ass;
            var xe = XElement.Load(fileName);
            Name = xe.Attribute("Name").Value;
            Fields = new Dictionary<string, Field>();
            foreach (var e in xe.Elements("Field"))
                Fields.Add(e.Attribute("Name").Value, new Field(e));
            Functions = new Dictionary<string, Function>();
            foreach (var e in xe.Elements("Function"))
                Functions.Add(e.Attribute("Name").Value, new Function(e));
            Properties = new Dictionary<string, Property>();
            foreach (var e in xe.Elements("Property"))
                Properties.Add(e.Attribute("Name").Value, new Property(e));
            IsCollection =
                Functions.ContainsKey("Item") &&
                Properties.ContainsKey("Count");
        }

        internal Type(Assembly ass, ComTypes.ITypeInfo info)
        {

            Assembly = ass;

            IntPtr ip;
            info.GetTypeAttr(out ip);
            var ar = Utils.GetObject<ComTypes.TYPEATTR>(ip);

            string name, doc, helpfile;
            int hc;
            info.GetDocumentation(-1, out name, out doc, out hc, out helpfile);
            Name = name;

            Fields = new Dictionary<string, Field>();
            for (int i = 0; i < ar.cVars; i++)
            {
                IntPtr aip;
                info.GetVarDesc(i, out aip);
                ComTypes.VARDESC vdesc =
                    Utils.GetObject<ComTypes.VARDESC>(aip);
                AddField(info, vdesc);
                info.ReleaseVarDesc(aip);
            }

            Functions = new Dictionary<string, Function>();
            Properties = new Dictionary<string, Property>();
            for (int i = 7; i < ar.cFuncs; i++)
            {
                IntPtr fip;
                info.GetFuncDesc(i, out fip);
                ComTypes.FUNCDESC fdesc =
                    Utils.GetObject<ComTypes.FUNCDESC>(fip);
                if (fdesc.invkind == ComTypes.INVOKEKIND.INVOKE_FUNC)
                    AddMethod(info, fdesc);
                else
                    AddProperty(info, fdesc);
                info.ReleaseFuncDesc(fip);
            }

            info.ReleaseTypeAttr(ip);

            IsCollection =
                Functions.ContainsKey("Item") &&
                Properties.ContainsKey("Count");

        }

        public void Save(string fileName)
        {
            XElement xe =
                new XElement("Type",
                    new XAttribute("Name", Name));
            foreach (var kv in Fields)
                xe.Add(kv.Value.Node);
            foreach (var kv in Functions)
                xe.Add(kv.Value.Node);
            foreach (var kv in Properties)
                xe.Add(kv.Value.Node);
            xe.Save(fileName);
        }

        private void AddField(ComTypes.ITypeInfo info, ComTypes.VARDESC desc)
        {
            var name = GetName(info, desc.memid);
            Field field =
                new Field(
                    desc.memid, name, info, desc);
            Fields.Add(name, field);
        }

        private void AddMethod(ComTypes.ITypeInfo info, ComTypes.FUNCDESC desc)
        {
            var names = GetNames(info, desc.memid);
            Functions.Add(
                names[0],
                new Function(
                    desc.memid, names, info, desc));
        }

        private void AddProperty(ComTypes.ITypeInfo info, ComTypes.FUNCDESC desc)
        {
            var name = GetName(info, desc.memid);
            if (Properties.ContainsKey(name))
            {
                Properties[name].MergeInvokeKind(desc.invkind);
            }
            else
            {
                Properties.Add(
                    name,
                    new Property(
                        desc.memid, name, info, desc));
            }
        }

        private string[] GetNames(ComTypes.ITypeInfo info, int id)
        {
            int count;
            var names = new string[255];
            info.GetNames(id, names, 255, out count);
            return names;
        }

        private string GetName(ComTypes.ITypeInfo info, int id)
        {
            int count;
            var names = new string[1];
            info.GetNames(id, names, 1, out count);
            return names[0];
        }

        public override string ToString()
        {
            return $"{Assembly}.{Name}";
        }

    }

}
